/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package image.utils;

import image.processing.ImageProcessor;
import image.processing.ImageProcessor.CompressAlgorithm;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.awt.image.ColorConvertOp;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import javax.imageio.ImageIO;
import javax.media.jai.BorderExtender;
import javax.media.jai.RenderedOp;
import javax.media.jai.operator.BorderDescriptor;

import net.sf.image4j.util.ConvertUtil;

/**
 *
 * @author tommy
 * Singleton class
 */
public class ImageUtilitiesImpl implements ImageUtilities {
 private static ImageUtilities INSTANCE = null;
 private static final String rawImageExtension = ".RAW";
 private static final String pgmImageExtension = ".PGM";
 
    // Private constructor suppresses 
    private ImageUtilitiesImpl() {}
 
    private synchronized static void createInstance() {
        if (INSTANCE == null) { 
			INSTANCE = new ImageUtilitiesImpl();
        }
    }
 
    public static ImageUtilities getInstance() {
        if (INSTANCE == null) {
			createInstance();
		}
        return INSTANCE;
    }
	
	private BufferedImage createImage(byte[] rawData, int width, int height, boolean isColor) throws ArrayIndexOutOfBoundsException {
		BufferedImage ret;
		int k=0;
		
		if (isColor)
			ret = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		else
			ret = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
		
		WritableRaster raster = ret.getRaster();


		for (int y = 0; y < height; y++) {
			for (int x = 0; x < width; x++) {
				if (isColor) {
					raster.setSample(x, y, 0, rawData[k]);
					raster.setSample(x, y, 1, rawData[k+1]);
					raster.setSample(x, y, 2, rawData[k+2]);
					 k+=2;
				} else {
					raster.setSample(x, y, 0, rawData[k++]);
				}
			}
		}

		return ret;
	}

	private byte[] getBytesFromFile(File file) throws IOException {
		InputStream is = new FileInputStream(file);

        // Get the size of the file
        long length = file.length();

        // You cannot create an array using a long type.
        // It needs to be an int type.
        // Before converting to an int type, check
        // to ensure that file is not larger than Integer.MAX_VALUE.
        if (length > Integer.MAX_VALUE) {
            // File is too large
        }

        // Create the byte array to hold the data
        byte[] bytes = new byte[(int)length];

        // Read in the bytes
        int offset = 0;
        int numRead = 0;
        while (offset < bytes.length
               && (numRead=is.read(bytes, offset, bytes.length-offset)) >= 0) {
            offset += numRead;
        }

        // Ensure all the bytes have been read in
        if (offset < bytes.length) {
            throw new IOException("Could not completely read file "+file.getName());
        }

        // Close the input stream and return bytes
        is.close();
        return bytes;
	}

	private byte[] getBytesFromFile(String path) throws IOException {
		File imageFile = new File(path);
		return getBytesFromFile(imageFile);
	}

	public BufferedImage openImage(File file) throws IOException {
		
		String fileName = file.getName();
		
		if (fileName.toUpperCase().endsWith(ImageUtilitiesImpl.rawImageExtension)) {
			throw new IOException("Unrecognizable image file format");
		} else {
			return ImageIO.read(file);
		}
	}
	
	public BufferedImage openRawImage(File file, int height, int width, boolean isColor) throws IOException, ArrayIndexOutOfBoundsException {
		return createImage(getBytesFromFile(file), width, height, isColor);
	}
	public BufferedImage openPgmImage(File file, boolean isColor) throws IOException{
		BufferedReader is = new BufferedReader(new FileReader(file));
		String line = is.readLine();
		int height;
		int width;
		int maxGray;
		if(!line.equals("P5")){
			System.err.println("not pgm");
			return null;
		}
		line = is.readLine();
		String[] size = line.split(" ");
		width = Integer.parseInt(size[0]);
		height = Integer.parseInt(size[1]);
		maxGray = Integer.parseInt(is.readLine());
		is.close();

		byte[] bytes = getBytesFromFile(file);
		byte[] byteaux = new byte[width * height];
		for (int i = 0; i < width * height; i++){
			byteaux[i] = bytes[15 + i];
		}
		
		return createImage(byteaux, width, height, isColor);
		
	}

	public boolean saveImage(File imageFile, BufferedImage img) {
		boolean isRaw = ImageExtensionUtils.raw.equals(ImageExtensionUtils.getExtension(imageFile));
		
		try {
			if (img.getType() == BufferedImage.TYPE_BYTE_GRAY && isRaw) {
				BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(imageFile));
				for (int x = 0; x < img.getWidth(); x++) {
					for (int y = 0; y < img.getHeight(); y++) {
						bos.write(img.getRaster().getSample(x, y, 0));
					}
				}
				bos.flush();
				bos.close();
			} else if (isRaw && img.getType() == BufferedImage.TYPE_3BYTE_BGR) {
				 return false;
			} else {
				ImageIO.write(img, ImageExtensionUtils.getExtension(imageFile).toUpperCase(), imageFile);
			}
		} catch (IOException ex) {
			return false;
		}
		return true;
	}

	public BufferedImage toPowerOfTwo(BufferedImage img) {
		int n,m, npowerTwo, mpowerTwo;
		
		if (img == null) {
			return null;
		}
		
		n = img.getWidth();
		m = img.getHeight();
		
		// If image is already squared, return original
		if (n % 2 == 0 && m % 2 == 0) {
			return img;
		}
		
		npowerTwo = getMinimunGreaterPowerOfTwo(n);
		mpowerTwo = getMinimunGreaterPowerOfTwo(m);
		
		return rebuildImage(img, Math.max(npowerTwo, mpowerTwo));
	}
	
	public BufferedImage addPaddingEdge(BufferedImage src, int n) {
		BufferedImage ret = null,aux;;
		System.out.println("srctype:= " + src.getType());
		// Adds n pixels as padding, copying original image edge value
		RenderedOp ro = BorderDescriptor.create(src, n, n, n, n, BorderExtender.createInstance(BorderExtender.BORDER_COPY), null);
		aux = ro.getAsBufferedImage();
		/**
		 * FIX: te cambia el tipo de la image, la vuelvo a convertir en BYTE GRAY
		 */
		if(src.getType() == BufferedImage.TYPE_BYTE_GRAY){
			ret = new BufferedImage(aux.getWidth(), aux.getHeight(),
					BufferedImage.TYPE_BYTE_GRAY);
			aux.copyData(ret.getRaster());
		}else{
			ret = aux;
		}
		System.out.println("rettype:= " + ret.getType());
		return ret;
	}
	
	public BufferedImage createDegrade(int height, int width) {
		BufferedImage ans = new BufferedImage(width, height,BufferedImage.TYPE_BYTE_GRAY);
		int values[][] = new int[ans.getWidth()][ans.getHeight()];
		double currentDegradeValue = 0;
		
		for (int x = 0; x < ans.getHeight(); x++) {
			for (int y = 0; y < ans.getWidth(); y++) {
				values[y][x] = (int) Math.floor(currentDegradeValue);
				currentDegradeValue += ((double) width) / 255;
			}
		}

		ans = ImageProcessor.generateCompressedImage1Channel(values, CompressAlgorithm.LINEAR);

		return ans;
	}
	
	public BufferedImage createDegrade(int height, int width, Color start, Color end) {
		BufferedImage ans = new BufferedImage(width, height,BufferedImage.TYPE_3BYTE_BGR);
		double[] currentDegradeValueRGB = new double[3];
		WritableRaster outRaster;
			
		outRaster = ans.getRaster();
		
		for (int y = 0; y < ans.getHeight(); y++) {
			// Linear interpolation of each channel for color degrading generation
			currentDegradeValueRGB[0] = Math.ceil((1d - y/(double)ans.getHeight()) * start.getRed() + y/(double)ans.getHeight() * end.getRed());
			currentDegradeValueRGB[1] = Math.ceil((1d - y/(double)ans.getHeight()) * start.getGreen() + y/(double)ans.getHeight() * end.getGreen());
			currentDegradeValueRGB[2] = Math.ceil((1d - y/(double)ans.getHeight()) * start.getBlue() + y/(double)ans.getHeight() * end.getBlue());
			
			for (int x = 0; x < ans.getWidth(); x++) {
				outRaster.setSample(x, y, 0, currentDegradeValueRGB[0]);
				outRaster.setSample(x, y, 1, currentDegradeValueRGB[1]);
				outRaster.setSample(x, y, 2, currentDegradeValueRGB[2]);
			}
			
			System.out.println("red: " + currentDegradeValueRGB[0] + " green: " + currentDegradeValueRGB[1] + " blue: " + currentDegradeValueRGB[2]);
		}

		//ans = ImageProcessor.generateCompressedImage1Channel(values, CompressAlgorithm.LINEAR);

		return ans;
	}
	
	private BufferedImage rebuildImage(BufferedImage src, int n) {
		BufferedImage ret;
		
		// Adds n pixels as padding, copying original image edge value
		RenderedOp ro = BorderDescriptor.create(src, n, n, n, n, BorderExtender.createInstance(BorderExtender.BORDER_ZERO), null);
		ret = ro.getAsBufferedImage();
		return ret;
	}
	
	private int getMinimunGreaterPowerOfTwo(int n) {
		int value = 1;
		
		while (value <= n) {
			value = value << 1;
		}
		return value;
	}

	public BufferedImage toGrayScale(BufferedImage src) {
		BufferedImage ret = new BufferedImage(src.getWidth(), src.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
		WritableRaster outRaster = ret.getRaster();
		Raster inRaster = src.getRaster();
		int r,g,b;
		
		if (src.getType() == BufferedImage.TYPE_BYTE_GRAY) {
			src.copyData(outRaster);
			return ret;
		} else if (src.getType() == BufferedImage.TYPE_3BYTE_BGR || 
			src.getType() == BufferedImage.TYPE_4BYTE_ABGR || src.getType() == BufferedImage.TYPE_INT_ARGB) {

			// using luminance algorithm, transforms image unknow
			for (int y = 0 ; y < src.getHeight() ; y++) {
				for (int x = 0 ; x < src.getWidth() ; x++) {
					r = inRaster.getSample(x, y, 0);
					g = r = inRaster.getSample(x, y, 1);
					b = inRaster.getSample(x, y, 2);
					outRaster.setSample(x, y, 0, 0.2126f*r + 0.7152f*g + 0.0722*b);
				}
			}
		} else if (src.getType() == BufferedImage.TYPE_BYTE_INDEXED) {
			ColorConvertOp op = new ColorConvertOp(
			src.getColorModel().getColorSpace(),
			ret.getColorModel().getColorSpace(),null);
			op.filter(src,ret);
		} else {
			throw new IllegalArgumentException("unknow image type");
		}
		
		return ret;
	}
	
	public BufferedImage toRGB(BufferedImage src){
		/*BufferedImage ret = new BufferedImage(src.getWidth(), src.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
		Raster inRaster = src.getRaster();
		int r[][],g[][],b[][];
		int sample;
		r=new int[src.getHeight()][src.getWidth()];
		g=new int[src.getHeight()][src.getWidth()];
		b=new int[src.getHeight()][src.getWidth()];
		if (src.getType() == BufferedImage.TYPE_BYTE_GRAY) {
			for (int y = 0 ; y < src.getHeight() ; y++) {
				for (int x = 0 ; x < src.getWidth() ; x++) {
					
					sample = inRaster.getSample(x, y, 0);
					r[x][y] = sample - (int)Math.floor(((double)0.7152f*sample + 0.0722f*sample)/0.2126f);
					g[x][y] = sample - (int)Math.floor(((double)0.2126f*sample + 0.0722f*sample)/0.7152f);
					b[x][y] = sample - (int)Math.floor(((double)0.2126f*sample + 0.7152f*sample)/0.0722f);

				}
			}
			return ImageProcessor.generateCompressedImage3Channel(r, g, b, CompressAlgorithm.LINEAR);
		} else if (src.getType() == BufferedImage.TYPE_3BYTE_BGR || 
			src.getType() == BufferedImage.TYPE_4BYTE_ABGR || src.getType() == BufferedImage.TYPE_INT_ARGB) {
			return src;
		}else {
			throw new IllegalArgumentException("unknow image tpye");
		}
		
		*/
		return ConvertUtil.convert24(src);
	}

	public BufferedImage blackImage(int n, int m) {
		BufferedImage img = new BufferedImage(n, m, BufferedImage.TYPE_BYTE_GRAY);
		WritableRaster outRaster = img.getRaster();
		
		for (int y = 0 ; y < m ; y++) {
			for (int x = 0 ; x < n ; x++) {
				outRaster.setSample(x, y, 0, 0);
			}
		}
		
		return img;
	}
	
}
